using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;

namespace HOPE {
	/// <summary>
	/// Class Player
	/// keempat karakter nantinya adalah instance dari class ini
	/// </summary>
	class Player {
		//Animation
#region Animation
#region Properties
		//Properties
		protected string IdleAnimation {
			get { return idleAnimationSprite; }
			set { idleAnimationSprite = value; }
		}
		protected string WalkAnimation {
			get { return walkAnimationSprite; }
			set { walkAnimationSprite = value; }
		}
		protected string RunAnimation {
			get { return runAnimationSprite; }
			set { runAnimationSprite = value; }
		}
#endregion
#region Fields
		private Animation walkAnimation;
		string walkAnimationSprite;
		private Animation runAnimation;
		string runAnimationSprite;
		private Animation jumpAnimation;
		private Animation idleAnimation;
		string idleAnimationSprite;
#endregion
#endregion
		private SpriteEffects flip = SpriteEffects.None;
		private AnimationPlayer sprite;

		//add property
		public Rectangle ThumbStickArea { get; set; }

		#region Sounds
		private SoundEffect jumpSound;
		private SoundEffect fallSound;
		private string fallSoundFile;
		string jumpSoundFile;

		protected string FallSoundFile {
			set { fallSoundFile = value; }
			get { return fallSoundFile; }
		}
		protected string JumpSoundFile {
			set { JumpSoundFile = value; }
			get { return jumpSoundFile; }
		}
		#endregion
		public Level Level {
			get { return level; }
		}
		Level level;

		public bool IsAlive {
			get { return isAlive; }
		}
		bool isAlive;

		//physic state
		public Vector2 Position {
			get { return position; }
			set { position = value; }
		}
		Vector2 position;

		private float previousBottom;

		public Vector2 Velocity {
			get { return velocity; }
			set { velocity = value; }
		}
		Vector2 velocity;

		//konstanta untuk control horizontal si hero
		private const float MoveAcceleration = 13000.0f;
		private const float MaxMoveSpeed = 2000.0f;
		private const float GroundDragFactor = 0.38f;
		private const float AirDragFactor = 0.48f;

		//konstanta untuk control vertical si hero
		private const float MaxJumpTime = 0.35f;
		private const float JumpLaunchVelocity = -3500.0f;
		private const float GravityAcceleration = 3400.0f;
		private const float MaxFallSpeed = 550.0f;
		private const float JumpControlPower = 0.14f;

		//input config
		private const float MoveStickScale = 1.0f;
		private const float AccelerometerScale = 1.5f;
		private const Buttons JumpButton = Buttons.A;
		private const Buttons ActionButton = Buttons.B;

		//mendeteksi apakah si hero ada di permukaan tanah atau tidak
		public bool IsOnGround {
			get { return isOnGround; }
		}
		bool isOnGround;

		//current user movement input
		private float movement;

		//jumping state
		private bool isJumping;
		private bool wasJumping;
		private float jumpTime;

		//action state
		private bool isAct;
		private bool wasAct;
		private float actTime;

		private Rectangle LocalBounds;

		//mendeteksi kotak benturan yang menyentuh si hero
		private Rectangle BoundingRectangle {
			get {
				int left = (int)Math.Round(Position.X - sprite.Origin.X) + LocalBounds.X;
				int top = (int)Math.Round(Position.Y - sprite.Origin.Y) + LocalBounds.Y;

				return new Rectangle(left, top, LocalBounds.Width, LocalBounds.Height);
			}
		}

		//constructor player baru
		public Player (Level level, Vector2 position) {
			this.level = level;

			LoadContent();

			Reset(position);
		}

		/// <summary>
		/// Loads the player sprite sheet and sounds.
		/// </summary>
		public void LoadContent () {
			//Load animated textures
			if (IdleAnimation != null) idleAnimation = new Animation(Level.Content.Load<Texture2D>(IdleAnimation), 0.1f, true);
			else throw new NullReferenceException("IdleAnimation has not been set.");
			if (RunAnimation != null) runAnimation = new Animation(Level.Content.Load<Texture2D>(RunAnimation), 0.1f, true);
			else throw new NullReferenceException("RunAnimation has not been set");
			//jumpAnimation = new Animation(Level.Content.Load<Texture2D>("Textures/Characters/Sprites/Hero/Audrianna/jump"), 0.1f, true);


			//calculate bounds within texture size.
			int width = (int)(idleAnimation.FrameWidth * 0.125);
			int left = (idleAnimation.FrameWidth - width) / 2;
			int height = (int)(idleAnimation.FrameWidth * 0.8);
			int top = idleAnimation.FrameHeight - height;
			LocalBounds = new Rectangle(left, top, width, height);

			//load sounds.
			if (JumpSoundFile != null)
				jumpSound = Level.Content.Load<SoundEffect>(JumpSoundFile);
			else throw new NullReferenceException("JumpSoundFile has not been set");
			if (FallSoundFile != null) fallSound = Level.Content.Load<SoundEffect>(FallSoundFile);
			else throw new NullReferenceException("FallSoundFile has not been set");

			//inisiasi ThumbStickArea
			ThumbStickArea = new Rectangle((int)HopeGame.controlstickBoundaryPosition.X,
										   (int)HopeGame.controlstickBoundaryPosition.Y,
										   HopeGame.controlstickBoundary.Width,
										   HopeGame.controlstickBoundary.Height);
		}

		/// <summary>
		/// Resets the player to life.
		/// </summary>
		/// <param name="position">The position to come to life at.</param>
		public void Reset (Vector2 position) {
			Position = position;
			Velocity = Vector2.Zero;
			isAlive = true;
			sprite.PlayAnimation(idleAnimation);
		}
		/// <summary>
		/// Handles input, performs physics, and animates the player sprite.
		/// </summary>
		/// <remarks>
		/// We pass in all of the input states so that our game is only polling the hardware
		/// once per frame. We also pass the game's orientation because when using the accelerometer,
		/// we need to reverse our motion when the orientation is in the LandscapeRight orientation.
		/// </remarks>
		public void Update (GameTime gameTime, KeyboardState keyboardState, GamePadState gamePadState, 
							TouchCollection touchState, AccelerometerState accelState, DisplayOrientation orientation) {
			//If you ever need to go back to old control scheme uncomment this version of GetInput
			//GetInput(keyboardState, gamePadState, touchState, accelState, orientation);

			//11a. Call our newly devised GetInput()
			GetInput();

			ApplyPhysics(gameTime);

			if (IsAlive && IsOnGround) {
				if (Math.Abs(Velocity.X) - 0.02f > 0) {
					sprite.PlayAnimation(runAnimation);
				}
				else {
					sprite.PlayAnimation(idleAnimation);
				}
			}

			// Clear input.
			movement = 0.0f;
			isJumping = false;
		}
        private void GetInput()
        {
            //Left ThumbStick to move player
            if (VirtualThumbsticks.LeftThumbstickCenter.HasValue)
            {
                Rectangle touchPointRectangle = new Rectangle((int)VirtualThumbsticks.LeftThumbstickCenter.Value.X,
                (int)VirtualThumbsticks.LeftThumbstickCenter.Value.Y, 1, 1);

                // If the touch is in the stick
                if (ThumbStickArea.Contains((int)VirtualThumbsticks.LeftThumbstickCenter.Value.X,
                                            (int)VirtualThumbsticks.LeftThumbstickCenter.Value.Y))
                {
                    //Get analog horizontal movement
                    movement = VirtualThumbsticks.LeftThumbstick.X * MoveStickScale;
                }
            }

			//if (VirtualThumbsticks.RightThumbstickCenter.HasValue)
			//{
			//    Rectangle buttonRectangle = new Rectangle((int)HopeGame.smokeButtonPosition.X, (int)HopeGame.smokeButtonPosition.Y,
			//                                                        HopeGame.smokeButton.Width / 2, HopeGame.smokeButton.Height);
			//    // Touch Bounds
			//    Rectangle touchRectangle = new Rectangle((int)VirtualThumbsticks.RightThumbstickCenter.Value.X,
			//                                            (int)VirtualThumbsticks.RightThumbstickCenter.Value.Y, 1, 1);
			//    // If the touch is in the button
			//    if (buttonRectangle.Contains(touchRectangle))
			//    {
			//        HopeGame.isSmokebuttonClicked = true;
			//    }
			//}
			////Check if player wants to Jump
			//isJumping = HopeGame.isSmokebuttonClicked;
        }
		/// <summary>
		/// Updates the player's velocity and position based on input, gravity, etc.
		/// </summary>
		public void ApplyPhysics (GameTime gameTime) {
			float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;

			Vector2 previousPosition = Position;

			// Base velocity is a combination of horizontal movement control and
			// acceleration downward due to gravity.
			velocity.X += movement * MoveAcceleration * elapsed;
			velocity.Y = MathHelper.Clamp(velocity.Y + GravityAcceleration * elapsed, -MaxFallSpeed, MaxFallSpeed);

			velocity.Y = DoJump(velocity.Y, gameTime);

			// Apply pseudo-drag horizontally.
			if (IsOnGround)
				velocity.X *= GroundDragFactor;
			else
				velocity.X *= AirDragFactor;

			// Prevent the player from running faster than his top speed.            
			velocity.X = MathHelper.Clamp(velocity.X, -MaxMoveSpeed, MaxMoveSpeed);

			// Apply velocity.
			Position += velocity * elapsed;
			Position = new Vector2((float)Math.Round(Position.X), (float)Math.Round(Position.Y));

			// If the player is now colliding with the level, separate them.
			HandleCollisions();

			// If the collision stopped us from moving, reset the velocity to zero.
			if (Position.X == previousPosition.X)
				velocity.X = 0;

			if (Position.Y == previousPosition.Y)
				velocity.Y = 0;
		}

		/// <summary>
		/// Calculates the Y velocity accounting for jumping and
		/// animates accordingly.
		/// </summary>
		/// <remarks>
		/// During the accent of a jump, the Y velocity is completely
		/// overridden by a power curve. During the decent, gravity takes
		/// over. The jump velocity is controlled by the jumpTime field
		/// which measures time into the accent of the current jump.
		/// </remarks>
		/// <param name="velocityY">
		/// The player's current velocity along the Y axis.
		/// </param>
		/// <returns>
		/// A new Y velocity if beginning or continuing a jump.
		/// Otherwise, the existing Y velocity.
		/// </returns>
		private float DoJump (float velocityY, GameTime gameTime) {
			// If the player wants to jump
			if (isJumping) {
				// Begin or continue a jump
				if ((!wasJumping && IsOnGround) || jumpTime > 0.0f) {
					if (jumpTime == 0.0f)
						jumpSound.Play();

					jumpTime += (float)gameTime.ElapsedGameTime.TotalSeconds;
					sprite.PlayAnimation(jumpAnimation);
				}

				// If we are in the ascent of the jump
				if (0.0f < jumpTime && jumpTime <= MaxJumpTime) {
					// Fully override the vertical velocity with a power curve that gives players more control over the top of the jump
					velocityY = JumpLaunchVelocity * (1.0f - (float)Math.Pow(jumpTime / MaxJumpTime, JumpControlPower));
				}
				else {
					// Reached the apex of the jump
					jumpTime = 0.0f;
				}
			}
			else {
				// Continues not jumping or cancels a jump in progress
				jumpTime = 0.0f;
			}
			wasJumping = isJumping;

			return velocityY;
		}

		/// <summary>
		/// Detects and resolves all collisions between the player and his neighboring
		/// tiles. When a collision is detected, the player is pushed away along one
		/// axis to prevent overlapping. There is some special logic for the Y axis to
		/// handle platforms which behave differently depending on direction of movement.
		/// </summary>
		private void HandleCollisions () {
			// Get the player's bounding rectangle and find neighboring tiles.
			Rectangle bounds = BoundingRectangle;
			int leftTile = (int)Math.Floor((float)bounds.Left / Tile.Width);
			int rightTile = (int)Math.Ceiling(((float)bounds.Right / Tile.Width)) - 1;
			int topTile = (int)Math.Floor((float)bounds.Top / Tile.Height);
			int bottomTile = (int)Math.Ceiling(((float)bounds.Bottom / Tile.Height)) - 1;

			// Reset flag to search for ground collision.
			isOnGround = false;

			// For each potentially colliding tile,
			for (int y = topTile; y <= bottomTile; ++y) {
				for (int x = leftTile; x <= rightTile; ++x) {
					// If this tile is collidable,
					TileCollision collision = Level.GetCollision(x, y);
					if (collision != TileCollision.Passable) {
						// Determine collision depth (with direction) and magnitude.
						Rectangle tileBounds = Level.GetBounds(x, y);
						Vector2 depth = RectangleExtensions.GetIntersectionDepth(bounds, tileBounds);
						if (depth != Vector2.Zero) {
							float absDepthX = Math.Abs(depth.X);
							float absDepthY = Math.Abs(depth.Y);

							// Resolve the collision along the shallow axis.
							if (absDepthY < absDepthX || collision == TileCollision.Platform) {
								// If we crossed the top of a tile, we are on the ground.
								if (previousBottom <= tileBounds.Top)
									isOnGround = true;

								// Ignore platforms, unless we are on the ground.
								if (collision == TileCollision.Impassable || IsOnGround) {
									// Resolve the collision along the Y axis.
									Position = new Vector2(Position.X, Position.Y + depth.Y);

									// Perform further collisions with the new bounds.
									bounds = BoundingRectangle;
								}
							}
							else if (collision == TileCollision.Impassable) // Ignore platforms.
                            {
								// Resolve the collision along the X axis.
								Position = new Vector2(Position.X + depth.X, Position.Y);

								// Perform further collisions with the new bounds.
								bounds = BoundingRectangle;
							}
						}
					}
				}
			}

			// Save the new bounds bottom.
			previousBottom = bounds.Bottom;
		}

		/// <summary>
		/// Called when the player has been killed.
		/// </summary>
		/// <param name="killedBy">
		/// The enemy who killed the player. This parameter is null if the player was
		/// not killed by an enemy (fell into a hole).
		/// </param>
		//public void OnKilled (Enemy killedBy) {
		//    isAlive = false;

		//    if (killedBy != null)
		//        killedSound.Play();
		//    else
		//        fallSound.Play();

		//    sprite.PlayAnimation(dieAnimation);
		//}

		/// <summary>
		/// Called when this player reaches the level's exit.
		/// </summary>
		//public void OnReachedExit () {
		//    sprite.PlayAnimation(celebrateAnimation);
		//}

		/// <summary>
		/// Draws the animated player.
		/// </summary>
		public void Draw (GameTime gameTime, SpriteBatch spriteBatch) {
			// Flip the sprite to face the way we are moving.
			if (Velocity.X > 0)
				flip = SpriteEffects.FlipHorizontally;
			else if (Velocity.X < 0)
				flip = SpriteEffects.None;

			// Draw that sprite.
			sprite.Draw(gameTime, spriteBatch, Position, flip);
		}

	}
}

